home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Commodore Free 30
/
Commodore_Free_Issue_30_2009_Commodore_Computer_Club.d64
/
c part 1.2
< prev
next >
Wrap
Text File
|
2023-02-26
|
16KB
|
623 lines
u
Alternative Programming Languages: C
Part 1 By Paul Davis
CONTINUED FROM PART 1.1
Formatted output
We have also introduced a new library
function here, printf. The 'f' stands
for 'formatted' & this function gives
us greater control over the layout of
the printed text. The first parameter
is a 'format string'. This is the text
we want to print with placeholders
(marked by a % sign) for the values we
want to display. These values are
passed as extra parameters to the
printf function. The first printf
instruction displays the value of the
'n' variable. We use a %d placeholder
to print the value of this variable as
a decimal number. The 2nd printf
displays the value of the 'c' variable
& uses the %c placeholder to display
it as a character. The 3rd printf
uses the %s placeholder to display the
contents of the 's' variable as a
string.
There is no special format placeholder
for an entire array so we use a loop
to print the values one at a time.
Notice this time we use %5d in the
format. This causes the numbers to be
printed in a 5 character wide field.
Any numbers shorter than 5 digits will
be padded with spaces on the left hand
side to make them line up.
There are many other formatting
options provided by the printf
function. I will leave it as an
exercise for the reader to explore
them in more detail. One important
thing to remember though is that
'printf' is a large, complex function
& will add a considerable amount to
the size of your program. If you need
to keep the size of your program down
you may prefer to use other library
routines for generating output.
Finally, notice the '\n' at the end of
each format string. This stands for
'new line' & moves the cursor down to
the start of the next line.
The for loop
The previous example also introduces
C's for loop. The syntax looks strange
at first but it's not too complicated.
The basic form is:
for (initialisation; condition;
increment)
There are three sections in the
brackets, separated by semi-colons.
The first section initialises the
values of any variables needed during
the loop, usually the loop counter.
In our example, we set the value of
'i', the loop index, to 0. The next
section is the condition. The loop
will run as long as this condition is
true. Note that the condition is
tested at the start of the loop so
it's possible for the body of the loop
to never run at all if the condition
is false the first time round. In our
example, the condition is 'i < 5'.
The loop will keep running while 'i'
is less than 5 & will terminate once
'i' is 5 or more. The final section of
a for loop is used to increment the
loop variable. Here we have used '++i'
to increment (add 1 to) the value of
'i'. The for loop here is therefore
roughly equivalent to the following
BASIC loop:
FOR I=0 TO 4:PRINT A(I):NEXT I
The console library
While the functions provided by the
'stdio' library are very useful, they
are general purpose routines designed
for writing & reading streams of data
to & from files. The screen just
happens to be one such type of data
stream. The cc65 compiler provides
another library called 'conio' that is
designed specifically for printing to
the screen & reading from the keyboard
(collectively known as the 'console').
These routines allow extra features
such as moving the cursor to a
specified location on the screen,
changing colours, waiting for a key
press etc. They are also usually
smaller & faster than the equivalent
'stdio' routines. Let's write a
program to demonstrate some of this
library's features.
Close the previous editor window &
create a new file called 'console.c'.
Enter the following program into this
file:
+
#include <conio.h>
!
void colors(int paper, int ink)
bgcolor(paper);
bordercolor(ink);
textcolor(ink);
clrscr();
!
void show_title_screen(void)
+
colors(COLOR_GRAY3, COLOR_GRAY1);
cputsxy(14, 12, "Welcome to C");
cgetc();
colors(COLOR_BLUE, COLOR_LIGHTBLUE);
!
void main(void)
+
show_title_screen();
!
Compile the program with 'cl65
console.c' & run it in VICE. The
program will change the screen colours
to grey & display a message at the
centre of the screen. It then waits
for a key to be pressed before
restoring the default colour scheme.
This program introduces a few new
concepts. Let's go through it. The
#include line reads the conio header
file so we can call the functions in
that library.
Next, we have created a function
called 'colors' to change the colours
on the screen (I used the American
spelling so the name is consistent
with the names of thelibrary
functions). The function takes two
colour parameters & uses these to
set the background, border & text
colours. It also clears the screen.
Notice how the parameters, just like
variables, must be declared as a
specific type.
The 2nd function, 'show_title_screen',
calls our 'colors' function to change
the screen to grey. We can use named
constants for the colours rather than
having to use literal numbers which
makes the program more readable. The
convention for naming constants is to
use all uppercase letters with words
separated by underscores. This makes
it easy to differentiate them from
variables.
The function 'cputsxy' is used to
print the message at the centre of the
screen. There are many such functions
in the conio library. Have a look
through the 'conio.h' file (in
c:\cc65\include or
/usr/local/lib/cc65/include) to see
all the functions this library
provides.
The 'cgetc' function waits for a key
to be pressed. It actually returns the
ASCII code of that key but we are
ignoring the return value in this
example. Once a key has been pressed,
the colours are restored to the
familiar blue on blue.
Notice how the program has been
organised. Each function performs a
small well-defined task, & the 'main'
function does little more than call
our other functions. It's good
practice to build programs up like
this, rather than just dumping
everything into the 'main' function.
Also notice that the functions are
defined before the point where they
are first used. Remember that C needs
to know what parameters a function
expects before it can call it. One way
to ensure this in your own program is
to place the function definition
before any code that calls it. C also
provides another, more flexible, way
to do this which we will look at next
time.
It would be nice if our program could
calculate the co-ordinates & centre
the text for us. Let's write a
function to do this. First, we need to
know the dimensions of the screen.
Okay, we know that the C64 screen is
25 lines of 40 columns, but if this
program were run on a C128 in 80
column mode that assumption wouldn't
work. Let's do it the proper way by
asking the conio library for the
dimensions of the screen.
First, add this line at the top of the
file under #include <conio.h>:
#include <string.h>
We need to call a function that is in
the string library so this line reads
the header file for that library. Now
add the following lines above the
'colors' function:
typedef unsigned char byte;
byte scr_width, scr_height;
void init(void)
+
screensize(&scr_width, &scr_height);
!
void center(char text[], byte y)
+
cputsxy((scr_width - strlen(text)) /
2,
y, text);
!
Here we have introduced a number of
new things. First is the 'typedef'
line. We are going to be using a lot
of variables of type 'unsigned char'
which is quite cumbersome to type (and
read). We can use 'typedef' to make a
new type name that is equivalent to
some other type. Here, we are telling
the compiler to allow us to use the
word 'byte' to mean 'unsigned char'
which makes our program much neater.
Now, using our new 'byte' type, we
create two global variables to hold
the width & height of the screen. The
variables need to be global so that
the other functions can use them.
Next, we create a function that
initialises these variables. using the
'screensize' function in the conio
library. The significance of the '&'
will be explained in more detail later
when we look at pointers. For now,
suffice it to say that the ampersands
allow the 'screensize' function to
modify the variables passed as
parameters. Remember that a C function
may only return a single value. This
'modifying the parameters' approach is
one way to get around that limitation.
The last new function is 'center'
which takes a text string & a line
number as parameters. The function
then uses 'cputsxy' to print the text
as before, but this time calculates
the x co-ordinate using the length of
the string (obtained using the
'strlen' library function) & the
screen width.
Now we need to change the line that
prints the message to use our new
center function:
center("Welcome to C", scr_height /
2);
And finally, change 'main' to call the
'init' function at the start of the
program so the screen size is set up
ready for the other functions to use:
void main(void)
+
init();
show_title_screen();
!
Save these changes, re-compile & run
the new program. The result should be
the same as before, but now we have a
general purpose function that can
centre text on any line of the screen.
The conio library also supports a
limited form of line drawing using the
Commodore's graphical characters.
Let's use these to put a box around
the edge of the screen. Add the
following function above the
'colors' function:
void box(byte x, byte y, byte w, byte
h)
+
cputcxy(x, y, CH_ULCORNER);
chline(w-2);
cputc(CH_URCORNER);
cvlinexy(x, y+1, h-2);
cputc(CH_LLCORNER);
chline(w-2);
cputc(CH_LRCORNER);
cvlinexy(x+w-1, y+1, h-2);
!
This function takes as parameters the
co-ordinates of the top left corner of
the box, its width & its height. It
uses a combination of 'cputc' to place
individual corner characters on the
screen & 'chline' and 'cvline' for
drawing horizontal & vertical lines
respectively.
Now change the start of the
'show_title_screen' function to use
our new box drawing capability:
colors(COLOR_GRAY3, COLOR_GRAY1);
box(0, 0, scr_width, scr_height);
box(9, 10, scr_width-18,
scr_height-20);
Re-compile & run the console program
again. Now the screen & message have
a box drawn around them. Try
experimenting with these functions to
create your own title screen. How
about changing the colours or having
multiple lines of centred text?
Strings
In the previous program we touched
briefly upon the string handling
library. Let's now look at strings in
more detail. As mentioned earlier, a
string is an array of characters. We
can create a string in C by enclosing
it in double quotes & access the
characters in it through the array.
For example, say we created a string
like this:
char s[] = "String";
We can access the individual
characters in the array through the
's' variable. The expression s[0]
would reference the first element in
the array, the letter 'S', the
expression s[1] would reference the
't' & so on.
How do we know when we have reached
the end of the string? C doesn't store
the length of a string, it uses a
terminating 'null' character to mark
the end of it instead. Every function
that manipulates strings needs to
check for this 'null' character
(ASCII code 0).
C provides a library of string
functions which we can access by
including the 'string.h' file in our
programs. You have already seen one
such function, called 'strlen' which
calculates the length of a string by
counting the characters until it
reaches the terminating 'null'
character.
One slightly awkward feature of the C
language is that the normal assignment
and comparison operators don't work
with strings. The expressions
'str1 = str2' or 'str1 < str2', for
example, don't work the way you might
expect. Instead, library functions
must be used to copy & compare strings
Let's create another program to
demonstrate some of the standard
string library functions that you
will use most often. Create a new
file called 'string.c' & enter the
following code into the file:
#include <stdio.h>
#include <string.h>
void main(void)
+
char str[] = "Commodore";
char copy[20];
printf("str: %s\n", str);
printf("len: %d\n", strlen(str));
strcpy(copy, str);
printf("\ncopy: %s\n", copy);
printf("len: %d\n", strlen(copy));
printf("cmp: %d\n", strcmp(str,
copy));
if (strcmp(str, copy) == 0)
+
puts("equal");
!
else
+
puts("not equal");
!
strcat(copy, " 64");
printf("\ncopy: %s\n", copy);
printf("len: %d\n", strlen(copy));
printf("cmp: %d\n", strcmp(str,
copy));
if (strcmp(str, copy) == 0)
+
puts("equal");
!
else
+
puts("not equal");
!
!
As usual, save the file, compile it
using 'cl65 string.c' & run it in
VICE. The program creates a string & a
second array that is used to store a
copy of that string. The 'strlen'
function is used to return the length
the string. The 'strcpy' function
copies the original string into the
'copy' array.
The 'strcmp' function is used to
compare two strings. It returns 0 if
the strings are equal, a number less
than 0 if the 1st string is less than
the 2nd, or a number greater than 0 if
the 1st string is greater than the 2nd
The 'if'statement compares the two
strings & displays 'equal'or 'not
equal' depending on the result. Notice
the use of the '==' operator to check
for equality. In C, the '=' operator
is only used for assigning values to
variables.
Finally, the program uses the 'strcat'
function to concatenate extra text to
the end of the copy string. See the
difference this makes to the output of
the 'strlen' & 'strcmp' values.
POKE & PEEK
The cc65 compiler provides a neat way
of performing POKE & PEEK operations.
Use #include <peekpoke.h> to get these
features:
POKE(address, value);
PEEK(address);
Now, you may be thinking that we can
use these for controlling the graphics
& sound capabilities of the C64.
Indeed we could, but cc65 provides a
better way to help us program the
custom chips by providing a named set
of variables for their registers.
These features are found in the header
file called 'c64.h'.
As an example, while we could change
the border colour using a POKE such as
this:
POKE(53280U, COLOR_BLACK);
A better way would be to use the
following instruction:
VIC.bordercolor = COLOR_BLACK;
This is more readable &, perhaps
surprisingly, is just as efficient as
the equivalent POKE.
VIC registers
To demonstrate some of these features,
here's a short program that displays a
sprite on the screen. Create a new
file called 'sprite.c' & enter the
following program into it:
#include <string.h>
#include <peekpoke.h>
#include <c64.h>
#define SPRITE0_DATA 0x0340
#define SPRITE0_PTR 0x07f8
void main(void)
+
memset((void*)SPRITE0_DATA, 0xff,
64);
POKE(SPRITE0_PTR, SPRITE0_DATA /
64);
VIC.spr0_color = COLOR_WHITE;
VIC.spr_hi_x = 0;
VIC.spr0_x = 100;
VIC.spr0_y = 100;
VIC.spr_ena = 1;
!
After including all the required
header files, this program uses
#define to create two constants that
are used to tell the VIC chip where
the sprite is stored in memory. The
'0x' prefix on the numbers tells C
they are in hexadecimal.
The main function uses 'memset' to
fill the sprite data memory with the
value 0xff (a byte with all bits
turned on). This results in a solid
block image. Next, the pointer for
sprite 0 is set to point to the sprite
data using a POKE. Then we use the
pre-defined variables to set the VIC
registers for sprite colour & position
and finally, make it visible.
Try experimenting a little with this
program. How about using a 'for' loop
to move the sprite around the screen?
If you're feeling adventurous, how
about writing a function that takes
the x & y co-ordinates as parameters
& moves the sprite to that position.
The function would have this type
signature:
void move_sprite(int x, int y)
The tricky bit is dealing with x
co-ordinates of 256 & above. If x is
less than 256, 'VIC.spr_hi_x' should
be 0 & 'VIC.spr0_x' should be x. But
when x is 256 or more, 'VIC.spr_hi_x'
should be 1 & 'VIC.spr0_x' should be
'x - 256'.
Next time
In the next article we will continue
with the sprite theme & see how to
build your own library of sprite
handling functions. We will also look
at how to integrate assembly language
into your C programs for extra speed.
See you then.
Article text & resources are now
available at:
http://sites.google.com/site/
develocity/commodore/articles
-------------------------